﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using Autofac;
using Autofac.Core;
using Cloak.Mvvm;
using Cloak.Mvvm.Navigation;

namespace Cloak.Autofac.Navigation
{
	/// <summary>
	/// A scope in which Autofac-registered view models are instantiated for pages
	/// </summary>
	public sealed class PageModelScope : IPageModelScope
	{
		private readonly ILifetimeScope _lifetimeScope;
		private bool _disposed;

		/// <summary>
		/// Initializes an instance of <see cref="PageModelScope"/>
		/// </summary>
		/// <param name="lifetimeScope">The Autofac lifetime scope encapsulated by this page model scope</param>
		public PageModelScope(ILifetimeScope lifetimeScope)
		{
			Contract.Requires(lifetimeScope != null);

			_lifetimeScope = lifetimeScope;
		}

		#region IDisposable
		/// <summary>
		/// Disposes the Autofac lifetime scope encapsulated by this page model scope
		/// </summary>
		public void Dispose()
		{
			if(!_disposed)
			{
				_disposed = true;

				_lifetimeScope.Dispose();
			}

			GC.SuppressFinalize(this);
		}
		#endregion

		#region IPageModelScope
		/// <summary>
		/// Resolves an instance of the referenced view model for the page at the specified URI and with the specified parameters
		/// </summary>
		/// <param name="pageUri">The URI of the page for which the view model is resolved</param>
		/// <param name="reference">The reference to the view model associated with the page</param>
		/// <param name="parameters">The parameters available when creating the view model</param>
		/// <returns>An instance of the reference view model</returns>
		public ViewModel ResolvePageModel(Uri pageUri, PageModelReference reference, IDictionary<string, string> parameters)
		{
			var namedReference = reference as NamedPageModelReference;

			return namedReference == null
				? ResolvePageModelByType(pageUri, reference, parameters)
				: ResolvePageModelByTypeAndName(pageUri, namedReference, parameters);
		}

		/// <summary>
		/// Creates an inner scope in the context of this scope
		/// </summary>
		/// <returns>A scope in the context of this scope</returns>
		public IPageModelScope BeginScope()
		{
			return new PageModelScope(_lifetimeScope.BeginLifetimeScope());
		}
		#endregion

		private ViewModel ResolvePageModelByType(Uri pageUri, PageModelReference reference, IDictionary<string, string> parameters)
		{
			return (ViewModel) _lifetimeScope.Resolve(reference.ViewModelType, GetParameters(pageUri, reference, parameters));
		}

		private ViewModel ResolvePageModelByTypeAndName(Uri pageUri, NamedPageModelReference namedReference, IDictionary<string, string> parameters)
		{
			return (ViewModel) _lifetimeScope.ResolveNamed(
				namedReference.ViewModelName,
				namedReference.ViewModelType,
				GetParameters(pageUri, namedReference, parameters));
		}

		private static IEnumerable<Parameter> GetParameters(Uri pageUri, PageModelReference reference, IDictionary<string, string> parameters)
		{
			return reference.ParseParameters(parameters).Concat(new[] { TypedParameter.From(pageUri) }).ToList();
		}
	}
}